package com.moss.cloud.common.security.component;

import com.moss.cloud.common.core.annotation.document.ApiComment;
import com.moss.cloud.common.core.annotation.document.ApiDoc;
import com.moss.cloud.common.core.model.Result;
import org.springframework.aop.framework.AopProxyUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationContext;
import org.springframework.core.ResolvableType;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.*;

import java.lang.reflect.*;
import java.util.*;

@Component
public class ApiDocGenerator {
    @Value("${spring.application.name}")
    private String name;
    private final ApplicationContext applicationContext;

    public ApiDocGenerator(ApplicationContext applicationContext) {
        this.applicationContext = applicationContext;
    }

    public List<Map<String, Object>> generateApiDocs() {
        List<Map<String, Object>> apiDocs = new ArrayList<>();
        Map<String, Object> controllers = applicationContext.getBeansWithAnnotation(RestController.class);

        for (Object controller : controllers.values()) {
            Class<?> controllerClass = AopProxyUtils.ultimateTargetClass(controller);
            ApiDoc controllerApiDoc = AnnotationUtils.findAnnotation(controllerClass, ApiDoc.class);
            if (controllerApiDoc != null) {
                String baseUrl = getBaseUrl(controllerClass);
                String group = controllerApiDoc.value();
                String groupNo = controllerApiDoc.groupNo();
                String appName = name;
                for (Method method : controllerClass.getDeclaredMethods()) {
                    ApiDoc methodApiDoc = AnnotationUtils.findAnnotation(method, ApiDoc.class);
                    if (methodApiDoc != null) {
                        Map<String, Object> apiInfo = generateApiInfo(method, baseUrl, group, groupNo, appName);
                        apiDocs.add(apiInfo);
                    }
                }
            }
        }
        return apiDocs;
    }

    private Map<String, Object> generateApiInfo(Method method, String baseUrl, String group, String groupNo, String appName) {
        Map<String, Object> apiInfo = new HashMap<>();
        ApiDoc apiDoc = AnnotationUtils.findAnnotation(method, ApiDoc.class);

        apiInfo.put("group", group);
        apiInfo.put("groupNo", groupNo);
        apiInfo.put("appName", appName);
        apiInfo.put("description", Objects.requireNonNull(apiDoc).value());
        apiInfo.put("path", getFullPath(baseUrl, method));
        apiInfo.put("method", getRequestMethod(method));
        apiInfo.put("parameters", getMethodParameters(method));
        Map<String, Object> result = new HashMap<>();
        result.put("code", "响应码，参考各自微服务响应码表");
        result.put("mesg", "响应信息");
        result.put("timestamp", "时间戳");
        result.put("data", getResponseInfo(method));
        result.put("success", "是否成功");
        result.put("fail", "是否失败");
        apiInfo.put("response", result);
        apiInfo.put("apiNo", apiDoc.apiNo());
        return apiInfo;
    }

    private String getBaseUrl(Class<?> controllerClass) {
        RequestMapping mapping = AnnotationUtils.findAnnotation(controllerClass, RequestMapping.class);
        if (mapping != null && mapping.value().length > 0) {
            return mapping.value()[0];
        }
        return "";
    }

    private String getFullPath(String baseUrl, Method method) {
        StringBuilder path = new StringBuilder(baseUrl);

        // 获取方法上的各种mapping注解
        GetMapping getMapping = AnnotationUtils.findAnnotation(method, GetMapping.class);
        PostMapping postMapping = AnnotationUtils.findAnnotation(method, PostMapping.class);
        PutMapping putMapping = AnnotationUtils.findAnnotation(method, PutMapping.class);
        DeleteMapping deleteMapping = AnnotationUtils.findAnnotation(method, DeleteMapping.class);
        RequestMapping requestMapping = AnnotationUtils.findAnnotation(method, RequestMapping.class);

        String methodPath = "";
        if (getMapping != null && getMapping.value().length > 0) {
            methodPath = getMapping.value()[0];
        } else if (postMapping != null && postMapping.value().length > 0) {
            methodPath = postMapping.value()[0];
        } else if (putMapping != null && putMapping.value().length > 0) {
            methodPath = putMapping.value()[0];
        } else if (deleteMapping != null && deleteMapping.value().length > 0) {
            methodPath = deleteMapping.value()[0];
        } else if (requestMapping != null && requestMapping.value().length > 0) {
            methodPath = requestMapping.value()[0];
        }

        if (!baseUrl.endsWith("/") && !methodPath.startsWith("/")) {
            path.append("/");
        }
        path.append(methodPath);
        return path.toString();
    }

    private String getRequestMethod(Method method) {
        if (AnnotationUtils.findAnnotation(method, GetMapping.class) != null) return "GET";
        if (AnnotationUtils.findAnnotation(method, PostMapping.class) != null) return "POST";
        if (AnnotationUtils.findAnnotation(method, PutMapping.class) != null) return "PUT";
        if (AnnotationUtils.findAnnotation(method, DeleteMapping.class) != null) return "DELETE";
        if (AnnotationUtils.findAnnotation(method, PatchMapping.class) != null) return "PATCH";
        return "GET";
    }

    private List<Map<String, Object>> getMethodParameters(Method method) {
        List<Map<String, Object>> parameters = new ArrayList<>();
        Parameter[] params = method.getParameters();

        for (int i = 0; i < params.length; i++) {
            Parameter param = params[i];
            Map<String, Object> paramInfo = new HashMap<>();

            // 获取参数名
            paramInfo.put("name", getParameterName(param));
            paramInfo.put("required", isParameterRequired(param));
            paramInfo.put("in", getParameterIn(param));

            // 处理参数类型
            ResolvableType paramType = ResolvableType.forMethodParameter(method, i);
            if (paramType != ResolvableType.NONE) {
                processType(paramType, paramInfo);
            } else {
                // 如果无法解析，则使用原始类型
                Class<?> paramClass = param.getType();
                paramInfo.put("type", paramClass.getSimpleName());
                if (isCustomType(paramClass)) {
                    paramInfo.put("structure", getTypeStructure(paramClass));
                }
            }

            parameters.add(paramInfo);
        }
        return parameters;
    }

    private void processType(ResolvableType type, Map<String, Object> info) {
        if (type.getType() instanceof Class) {
            Class<?> clazz = (Class<?>) type.getType();
            info.put("type", clazz.getSimpleName());
            if (isCustomType(clazz)) {
                info.put("structure", getTypeStructure(clazz));
            }
        } else if (type.getType() instanceof ParameterizedType) {
            ParameterizedType parameterizedType = (ParameterizedType) type.getType();
            String classTypeName = formatGenericTypeName(parameterizedType);
            info.put("type", classTypeName);
            List<Map<String, Object>> genericStructures = new ArrayList<>();
            for (ResolvableType genericType : type.getGenerics()) {
                Map<String, Object> genericInfo = new HashMap<>();
                processType(genericType, genericInfo);
                genericStructures.add(genericInfo);
                if (classTypeName.contains("IPage")) {
                    Map<String, Object> page = new HashMap<>();
                    page.put("records", genericStructures);
                    page.put("total", "总条数");
                    page.put("size", "每页显示条数");
                    page.put("current", "当前页");
                    page.put("pages", "总页数");
                    info.put("genericStructures", page);
                } else {
                    info.put("genericStructures", genericStructures);
                }
            }
        } else if (type.getType() instanceof TypeVariable) {
            TypeVariable<?> typeVariable = (TypeVariable<?>) type.getType();
            info.put("type", typeVariable.getName());
        }
    }

    private String getParameterName(Parameter param) {
        // 尝试从注解中获取参数名
        RequestParam requestParam = param.getAnnotation(RequestParam.class);
        if (requestParam != null && !requestParam.value().isEmpty()) {
            return requestParam.value();
        }

        PathVariable pathVariable = param.getAnnotation(PathVariable.class);
        if (pathVariable != null && !pathVariable.value().isEmpty()) {
            return pathVariable.value();
        }

        // 如果注解中没有指定名称，则使用参数名
        return param.getName();
    }

    private boolean isParameterRequired(Parameter param) {
        RequestParam requestParam = param.getAnnotation(RequestParam.class);
        if (requestParam != null) {
            return requestParam.required();
        }
        PathVariable pathVariable = param.getAnnotation(PathVariable.class);
        if (pathVariable != null) {
            return pathVariable.required();
        }
        RequestBody requestBody = param.getAnnotation(RequestBody.class);
        if (requestBody != null) {
            return requestBody.required();
        }
        return false;
    }

    private String getParameterIn(Parameter param) {
        if (param.getAnnotation(RequestBody.class) != null) return "body";
        if (param.getAnnotation(PathVariable.class) != null) return "path";
        if (param.getAnnotation(RequestParam.class) != null) return "query";
        return "query";
    }

    private Map<String, Object> getResponseInfo(Method method) {
        Map<String, Object> responseInfo = new HashMap<>();
        ResolvableType resolvableType = ResolvableType.forMethodReturnType(method, method.getDeclaringClass());

        if (resolvableType != ResolvableType.NONE) {
            Class<?> rawType = resolvableType.getRawClass();

            if (rawType != null && Result.class.isAssignableFrom(rawType)) {
                // 处理 Result<T>
                responseInfo.put("type", rawType.getSimpleName());
                ResolvableType responseType = resolvableType.getGeneric(0);
                if (responseType != ResolvableType.NONE) {
                    processType(responseType, responseInfo);
                }
            } else {
                // 处理非 Result<T>
                responseInfo.put("type", rawType.getSimpleName());
                processType(resolvableType, responseInfo);
            }
        }

        return responseInfo;
    }

    private String formatGenericTypeName(ParameterizedType type) {
        StringBuilder sb = new StringBuilder();
        sb.append(((Class<?>) type.getRawType()).getSimpleName());
        sb.append("<");

        Type[] typeArgs = type.getActualTypeArguments();
        for (int i = 0; i < typeArgs.length; i++) {
            if (i > 0) {
                sb.append(", ");
            }
            if (typeArgs[i] instanceof Class) {
                sb.append(((Class<?>) typeArgs[i]).getSimpleName());
            } else if (typeArgs[i] instanceof ParameterizedType) {
                sb.append(formatGenericTypeName((ParameterizedType) typeArgs[i]));
            } else if (typeArgs[i] instanceof TypeVariable) {
                sb.append(((TypeVariable<?>) typeArgs[i]).getName());
            }
        }

        sb.append(">");
        return sb.toString();
    }

    private Map<String, Object> getTypeStructure(Class<?> clazz) {
        Map<String, Object> structure = new HashMap<>();

        ApiComment classComment = clazz.getAnnotation(ApiComment.class);
        if (classComment != null) {
            structure.put("description", classComment.value());
        }

        List<Map<String, Object>> fields = getTypeFields(clazz);
        structure.put("fields", fields);
        return structure;
    }

    private boolean isCustomType(Class<?> type) {
        return !type.isPrimitive() &&
                !type.getName().startsWith("java.lang") &&
                !type.getName().startsWith("java.util") &&
                !type.isArray();
    }

    private List<Map<String, Object>> getTypeFields(Class<?> type) {
        List<Map<String, Object>> fields = new ArrayList<>();
        for (Field field : type.getDeclaredFields()) {
            ApiComment comment = field.getAnnotation(ApiComment.class);
            if (comment != null) {
                Map<String, Object> fieldInfo = getFieldInfo(field);
                if (fieldInfo != null) {
                    fields.add(fieldInfo);
                }
            }
        }
        return fields;
    }

    private Map<String, Object> getFieldInfo(Field field) {
        ApiComment comment = field.getAnnotation(ApiComment.class);
        if (comment == null) {
            return null;
        }

        Map<String, Object> fieldInfo = new HashMap<>();
        fieldInfo.put("name", field.getName());
        fieldInfo.put("description", comment.value());
        fieldInfo.put("required", comment.required());
        fieldInfo.put("example", comment.example());

        // 处理字段类型
        ResolvableType fieldType = ResolvableType.forField(field);
        if (fieldType != ResolvableType.NONE) {
            processType(fieldType, fieldInfo);
        } else {
            // 如果无法解析，则使用原始类型
            Class<?> fieldClass = field.getType();
            fieldInfo.put("type", fieldClass.getSimpleName());
            if (isCustomType(fieldClass)) {
                fieldInfo.put("structure", getTypeStructure(fieldClass));
            }
        }

        return fieldInfo;
    }
}
